<?php
/**
 * Shipping Agent for Magento Commerce implementation.
 *
 * @package shipping_agent
 * @copyright Copyright 2009 Structured Solutions
 * @license http://www.codeplex.com/ShippingAgent/license Microsoft Public License (Ms-PL)
 * @version 3.3
 */

/**
 * Load Magento Commerce.
 */
require_once 'app/Mage.php';
Mage::app();

/**
 * SOAP classes from shippingagent.wsdl
 */
require_once("shippingagent_soap.php");

/**
 * shipping_agent_impl Class.
 * Implements the SOAP interface defined by shipping_agent.wsdl.
 * @package shipping_agent
 * @subpackage classes
 * @author Structured Solutions
 */
class shippingagent_impl
{
    /**
     * The contract version implemented by this class.
     * @var string
     */
    const VERSION = "3.3.0";

	/**
	 * The default value for ShippingAddress.Residential. If you want shipping
     * addresses to default to residential, change this constant to true.
	 * @var bool
	 */
	const SHIPPING_ADDRESS_RESIDENTIAL = false;

	/**
	 * Magento does not maintain a separate Order Payment Status, so
	 * the Order.PaymentStatus is calculated by buildPaymentStatus.
	 */
	const PAYMENT_STATUS_UNPAID = "Unpaid";
	const PAYMENT_STATUS_PARTIALLY_PAID = "Partially Paid";
	const PAYMENT_STATUS_PAID = "Paid";

	/**
	 * Magento does not maintain a separate Order Shipping Status, so
	 * the Order.ShippingStatus is calculated by buildShippingStatus.
	 */
	const SHIPPING_STATUS_UNSHIPPABLE = "Unshippable";
	const SHIPPING_STATUS_UNSHIPPED = "Unshipped";
	const SHIPPING_STATUS_PARTIALLY_SHIPPED = "Partially Shipped";
	const SHIPPING_STATUS_SHIPPED = "Shipped";

	/**
	 * Return new orders. "New" in this implementation means orders with an
     * order id greater than the value in pageKey. If pageKey is null, this
     * implementation will return orders placed today.
	 *
	 * @param stdClass $parameters
     * $parameters->pageKey is the id of the last order in the previous call.
	 * $parameters->maxCount is the maximum number of orders to return.
     * @return GetNewOrdersResponse
	 *
	 * If Remaining > 0, the client will typically call this method again to
     * retrieve the next page of results.
	 */
	public function GetNewOrders(stdClass $parameters)
	{
		try
		{
			$mcOrders = Mage::getModel('sales/order')->getCollection();

			if (isset($parameters->pageKey) && $parameters->pageKey != "")
			{
				$mcOrders->addAttributeToFilter($mcOrders->getEntity()->getIdFieldName(), array("gt"=>$parameters->pageKey));
			}
			else
			{
		        $mcOrders->addAttributeToFilter("created_at", array("date"=>true, "from"=>new Zend_Date()));
			}

		    $response = new GetNewOrdersResponse();
		    $response->GetNewOrdersResult = new OrderResultsPage();

		    // As of 1.2.1.2 the following sort order has no affect on getAllIds
			$mcOrders->setOrder($mcOrders->getEntity()->getIdFieldName(), Varien_Data_Collection::SORT_ORDER_ASC);
			foreach ($mcOrders->getAllIds($parameters->maxCount) as $mcOrderId) {
		    	$mcOrder = Mage::getModel('sales/order');
				$mcOrder->load($mcOrderId);
		    	$response->GetNewOrdersResult->OrderResults[] = $this->BuildOrderResult($mcOrder);
			}

			$response->GetNewOrdersResult->Key = $mcOrderId;
			$response->GetNewOrdersResult->Remaining =
				$mcOrders->getSize() -
				count($response->GetNewOrdersResult->OrderResults);
			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot return new orders. " . $ex->getMessage());
		}
	}

	/**
	 * Return one Order.
	 * $parameters->orderId is the ID of the order to retrieve.
	 * @param stdClass $parameters
	 * @return GetOrderResponse
	 */
	public function GetOrder(stdClass $parameters)
	{
		if (!isset($parameters->orderId) || trim($parameters->orderId) == '')
			throw new SoapFault("Client", "Missing order ID.");

		try
		{
			$mcOrder = Mage::getModel('sales/order')->load($parameters->orderId);
			if ($mcOrder->getId() != $parameters->orderId)
				throw new Exception("Invalid Order ID '" . $parameters->orderId . "'.");

			$response = new GetOrderResponse();
			$response->GetOrderResult = $this->buildOrderResult($mcOrder);

			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve order. " . $ex->getMessage());
		}
	}

	/**
	 * Return the current inventory level of each item in the order.
	 * $parameters->orderId is the ID of the order to retrieve.
	 * @param stdClass $parameters
	 * @return GetOrderInventoryResponse
	 */
	public function GetOrderInventory(stdClass $parameters)
	{
		if (!isset($parameters->orderId) || trim($parameters->orderId) == '')
			throw new SoapFault("Client", "Missing order ID.");

		try
		{
			$mcOrder = Mage::getModel('sales/order');
			$mcOrder->load($parameters->orderId);
			if ($mcOrder->getId() != $parameters->orderId)
				throw new Exception("Invalid Order ID '" . $parameters->orderId . "'.");

			$response = new GetOrderInventoryResponse();
			$response->GetOrderInventoryResult = $this->buildOrderInventoryResult($mcOrder);

			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve order inventory. " . $ex->getMessage());
		}
	}

	/**
	 * Return the orders that meet the criteria specified in the parameters.
	 * $parameters->startOrderNumber is the first order number to retrieve. If null, start at the first order.
	 *
	 * $parameters->endOrderNumber is the last order number to retrieve. If null, end at the most recent order.
	 *
	 * $parameters->startOrderDate is the beginning of the date range to search. If null, ignore the beginning date.
	 *
	 * $parameters->endOrderDate is the end of the date range to search. If null, ignore the end date. The
	 * endOrderDate does not include a time component, but the client assumes the result will include orders placed
	 * on the endOrderDate.
	 *
	 * $parameters->pageKey is a key used by Shipping Agent to determine which page of the results to return. If null, return the first page.
	 *
	 * $parameters->maxCount is the maximum number of orders to return.
	 *
	 * @param stdClass $parameters
	 * @return GetOrdersByRangeResponse
	 *
	 * If Remaining > 0, the client will call this method again to retrieve the
	 * next page of results.
	 */
	public function GetOrdersByRange(stdClass $parameters)
	{
		try
		{
			$mcOrders = Mage::getModel('sales/order')->getCollection();

			if (isset($parameters->startOrderNumber))
			{
				$mcOrders->addAttributeToFilter("increment_id", array("from"=>$parameters->startOrderNumber));
			}

			if (isset($parameters->endOrderNumber))
			{
				$mcOrders->addAttributeToFilter("increment_id", array("to"=>$parameters->endOrderNumber));
			}

			if (isset($parameters->startOrderDate))
			{
				/**
				 *  The UTC date from Shipper does not include the timezone for the website,
				 *  even though the human on the other end thinks of it that way. So this code
				 *  calculates the GMT offset for the date and then adds that back in.
				 */
		        $date = new Zend_Date();
		        $date->setIso($parameters->startOrderDate);
		        $offset = Mage::app()->getLocale()->storeDate(Mage::app()->getStore(), $date, true)->getGmtOffset();
		        $date->addTimestamp($offset);
		        $mcOrders->addAttributeToFilter("created_at", array("datetime"=>true, "from"=>$date));
			}

			if (isset($parameters->endOrderDate))
			{
				/**
				 *  The UTC date from Shipper does not include the timezone for the website,
				 *  even though the human on the other end thinks of it that way. So this code
				 *  calculates the GMT offset for the date and then adds that back in.
				 */
		        $date = new Zend_Date();
		        $date->setIso($parameters->endOrderDate);
		        $date->addDay(1);
		        $offset = Mage::app()->getLocale()->storeDate(Mage::app()->getStore(), $date, true)->getGmtOffset();
		        $date->addTimestamp($offset);
		        $mcOrders->addAttributeToFilter("created_at", array("datetime"=>true, "to"=>$date));
			}

			if (isset($parameters->pageKey) && $parameters->pageKey != "")
			{
				$mcOrders->addAttributeToFilter($mcOrders->getEntity()->getIdFieldName(), array("gt"=>$parameters->pageKey));
			}

		    $response = new GetOrdersByRangeResponse();
		    $response->GetOrdersByRangeResult = new OrderResultsPage();

		    // As of 1.2.1.2 the following has no affect on getAllIds
			$mcOrders->setOrder($mcOrders->getEntity()->getIdFieldName(), Varien_Data_Collection::SORT_ORDER_ASC);
		    foreach ($mcOrders->getAllIds($parameters->maxCount) as $mcOrderId) {
		    	$mcOrder = Mage::getModel('sales/order');
				$mcOrder->load($mcOrderId);
		    	$response->GetOrdersByRangeResult->OrderResults[] = $this->BuildOrderResult($mcOrder);
			}

			$response->GetOrdersByRangeResult->Key = $mcOrderId;
			$response->GetOrdersByRangeResult->Remaining =
				$mcOrders->getSize() -
				count($response->GetOrdersByRangeResult->OrderResults);
			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve orders by range. " . $ex->getMessage());
		}
	}

	/**
	 * Return one package.
	 *
	 * $parameters->orderId is the ID of the Magento order the package belongs to.
	 *
	 * $parameters->packageId is the ID of the Magento shipment. If this parameter
	 * is empty, then return the default shipment for the order.
	 *
	 * @param stdClass $parameters
	 * @return GetPackageResponse
	 */
	public function GetPackage(stdClass $parameters)
	{
		if (!isset($parameters->orderId) || trim($parameters->orderId) == '')
			throw new SoapFault("Client", "Missing order ID.");

		try
		{
			$mcOrder = Mage::getModel('sales/order')->load($parameters->orderId);
			if ($mcOrder->getId() != $parameters->orderId) {
				throw new Exception("Invalid Order ID '" . $parameters->orderId . "'.");
			}

			if (isset($parameters->packageId) && strpos($parameters->packageId, ",")) {
                $ids = explode(",", $parameters->packageId);
                $mcShipmentId = trim($ids[0]);
                $mcTrackId = trim($ids[1]);
            }

			if (isset($mcShipmentId)) {
				$mcShipment = Mage::getModel('sales/order_shipment')->load($mcShipmentId);
				if ($mcShipment->getId() != $mcShipmentId) {
					throw new Exception("Invalid Package ID '" . $parameters->packageId . "'.");
                }
			} else {
				$mcShipment = $this->buildShipment($mcOrder);
				if ($mcShipment == null) {
					$response = new GetPackageResponse();
					$response->GetPackageResult = new PackageResult();
					$response->GetPackageResult->Success = true;
					return $response;
                }
			}

            if (isset($mcTrackId)) {
                $mcTrack = Mage::getModel("sales/order_shipment_track")->load($mcTrackId);
                if ($mcTrack->getId() != $mcTrackId || $mcTrack->getShipment()->getId() != $mcShipmentId) {
                    throw new Exception("Invalid Package ID '" . $parameters->packageId . "'.");
                }
            } else if (count($mcShipment->getAllTracks()) == 0) {
                $mcTrack = Mage::getModel("sales/order_shipment_track");
                $mcShipment->addTrack($mcTrack);
            } else {
                $mcTracks = $mcShipment->getAllTracks();
                $mcTrack = $mcTracks[0];
            }

            $response = new GetPackageResponse();
            $response->GetPackageResult = $this->buildPackageResult($mcShipment, $mcTrack);
            return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve package. " . $ex->getMessage());
		}
	}

	/**
	 * Return the complete list of tracking providers supported by this cart.
	 * @return GetTrackingProvidersResponse
	 */
	public function GetTrackingProviders()
	{
		try
		{
			$response = new GetTrackingProvidersResponse();
			$response->GetTrackingProvidersResult = $this->buildTrackingProvidersResult();
			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve tracking providers. " . $ex->getMessage());
		}
	}

	/**
	 * Return the WSDL version implemented by this Shipping Agent.
	 * @return string
	 */
	public function GetVersion()
	{
		$response = new GetVersionResponse();
		$response->GetVersionResult = self::VERSION;
		return $response;
	}

	/**
	 * Update the package with tracking information.
	 * @param stdClass $parameters
	 * @return UpdatePackageResponse
	 */
	public function UpdatePackage(stdClass $parameters)
	{
		if (!isset($parameters->update))
			return null;

		if (!isset($parameters->update->OrderId) || trim($parameters->update->OrderId) == '')
			throw new SoapFault("Client", "Missing order ID.");

		try
		{
			$response = new UpdatePackageResponse();
			$response->UpdatePackageResult = $this->buildUpdateResult($parameters->update);

			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot update package. " . $ex->getMessage());
		}
	}

	/**
	 * Compare two addresses and return true if equal.
	 * @param stdClass $address
	 * @param Mage_Sales_Model_Order_Address $mcAddress
	 */
	private function addressesAreEqual(stdClass $address, Mage_Sales_Model_Order_Address $mcAddress)
	{
		if (isset($address->City)) {
            if ($address->City != $mcAddress->getCity()) return false;
        } else {
            if ("" != $mcAddress->getCity()) return false;
        }
		if (isset($address->Company)) {
            if ($address->Company != $mcAddress->getCompany()) return false;
        } else {
            if ("" != $mcAddress->getCompany()) return false;
        }
		if (isset($address->Country)) {
            if ($address->Country != $mcAddress->getCountry()) return false;
        } else {
            if ("" != $mcAddress->getCountry()) return false;
        }
		if (isset($address->Fax)) {
            if ($address->Fax != $mcAddress->getFax()) return false;
        } else {
            if ("" != $mcAddress->getFax()) return false;
        }
		if (isset($address->FirstName)) {
            if ($address->FirstName != $mcAddress->getFirstname()) return false;
        } else {
            if ("" != $mcAddress->getFirstname()) return false;
        }
		if (isset($address->LastName)) {
            if ($address->LastName != $mcAddress->getLastname()) return false;
        } else {
            if ("" != $mcAddress->getLastname()) return false;
        }
		if (isset($address->Line1)) {
            if ($address->Line1 != $mcAddress->getStreet1()) return false;
        } else {
            if ("" != $mcAddress->getStreet1()) return false;
        }
		if (isset($address->Line2)) {
            if ($address->Line2 != $mcAddress->getStreet2()) return false;
        } else {
            if ("" != $mcAddress->getStreet2()) return false;
        }
        if (isset($address->Phone)) {
            if ($address->Phone != $mcAddress->getTelephone()) return false;
        } else {
            if ("" != $mcAddress->getTelephone()) return false;
        }
		if (isset($address->PostalCode)) {
            if ($address->PostalCode != $mcAddress->getPostcode()) return false;
        } else {
            if ("" != $mcAddress->getPostcode()) return false;
        }
		if (isset($address->Region)) {
            if ($address->Region != $mcAddress->getRegionCode()) return false;
        } else {
            if ("" != $mcAddress->getRegionCode()) return false;
        }
		return true;
	}

	/**
	 * Build a shipping address from an order address array.
	 * Note that Shipper expects 2 letter country, state, and
	 * province codes.
	 * @param Mage_Sales_Model_Order_Address $mcAddress
	 * @return ShippingAddress
	 */
	private function buildAddress(Mage_Sales_Model_Order_Address $mcAddress)
	{
		$address = new Address();
		$address->AddressId = $mcAddress->getId();
		$address->City = $mcAddress->getCity();
		$address->Company = $mcAddress->getCompany();
		$address->Country = $mcAddress->getCountry();
		$address->Email = $mcAddress->getEmail();
		$address->Fax = $mcAddress->getFax();
		$address->FirstName = $mcAddress->getFirstname();
		$address->LastName = $mcAddress->getLastname();
		$address->Line1 = $mcAddress->getStreet1();
		$address->Line2 = $mcAddress->getStreet2();
		$address->Phone = $mcAddress->getTelephone();
		$address->PostalCode = $mcAddress->getPostcode();
		$address->Region = $mcAddress->getRegionCode();
		return $address;
	}

	/**
	 * Build a list of orders charges.
	 * Discounts are included as order items. Gift Certificates are treated as payments.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array
	 */
	private function buildCharges(Mage_Sales_Model_Order $mcOrder)
	{
		$charges = array();
		$amount = $this->getFloat($mcOrder->getShippingAmount());
		if ($amount != (float) 0)
		{
			$namedAmount = new NamedAmount();
			$namedAmount->Amount = $amount;
			$namedAmount->Name = "Shipping";
			$charges[] = $namedAmount;
		}
		$amount = $this->getFloat($mcOrder->getTaxAmount());
		if ($amount != (float) 0)
		{
			$namedAmount = new NamedAmount();
			$namedAmount->Amount = $amount;
			$namedAmount->Name = "Tax";
			$charges[] = $namedAmount;
		}

		return $charges;
	}

	/**
	 * Build a Customer from an order.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return Customer
	 */
	private function buildCustomer(Mage_Sales_Model_Order $mcOrder)
	{
		$customer = new Customer();
		if ($mcOrder->getCustomerIsGuest())
		{
			$customer->CustomerId = "guest";
		}
		else
		{
			$customer->CustomerId = $mcOrder->getCustomerId();
			$customer->CustomerUrl = $this->buildCustomerUrl("index.php/customer/account/");
			$customer->Email = $mcOrder->getCustomerEmail();
			$customer->FirstName = $mcOrder->getCustomerFirstname();
			$customer->LastName = $mcOrder->getCustomerLastname();
			$customer->MerchantUrl = $this->buildMerchantUrl("index.php/admin/customer/edit/id/" . $mcOrder->getCustomerId() . "/");
			$customer->UserName = $mcOrder->getCustomerEmail();
		}
		return $customer;
	}

	/**
	 * Build a URL to the customer's view of the page.
	 * @param string $page
	 * @param string $query
	 * @return string
	 */
	private function buildCustomerUrl($page, $query = null)
	{
		$url = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB) . $page;
		if (isset($query) && trim($query) != "")
			$url = $url . "?" . $query;
		return $url;
	}

	/**
	 * Create an Item from a Magento item.
	 *
	 * @param Mage_Sales_Model_Order_Item $mcItem
	 * @return Item
	 */
	private function buildItem(Mage_Sales_Model_Order_Item $mcOrderItem)
	{
		$item = new Item();
		$item->Code = $this->buildItemCode($mcOrderItem);
		$item->Description = $this->buildItemDescription($mcOrderItem);
		$item->Inventory = $this->buildItemInventory($mcOrderItem);
		$item->ItemId = $mcOrderItem->getId();
		$item->LineTotal = $mcOrderItem->getRowTotal();
		$item->Name = $mcOrderItem->getName();
		$item->Price = $mcOrderItem->getPrice();
		$item->Quantity = $mcOrderItem->getQtyOrdered();
		$item->Shippable = $mcOrderItem->getIsVirtual() == 0;
		$item->Weight = $mcOrderItem->getQtyOrdered() * $mcOrderItem->getWeight();
		return $item;
	}

	/**
	 * Return the Magento Item SKU
	 *
	 * @param Mage_Sales_Model_Order_Item $mcItem
	 * @return string
	 */
	private function buildItemCode(Mage_Sales_Model_Order_Item $mcItem)
	{
        if ($mcItem->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
            return $mcItem->getProductOptionByCode('simple_sku');
        }
        return $mcItem->getSku();
	}

	/**
	 * Build up the item description.
	 *
	 * @param Mage_Sales_Model_Order_Item $mcItem
	 * @return string
	 */
	private function buildItemDescription(Mage_Sales_Model_Order_Item $mcItem)
	{
		$desc = array();

		foreach ($this->buildItemOptions($mcItem) as $option) {
            $label = $option["label"];
            $value = $option["value"];
            if (is_array($value)) {
                $_desc = array();
                foreach ($value as $_value) {
                    $_desc[] = $_value["qty"] . "x" . $_value["title"];
                }
                $value = implode("; ", $_desc);
            }
			$desc[] = $label . ": " . $value;
		}

//		if ($mcItem->getHasChildren()) {
//			foreach ($mcItem->getChildrenItems() as $mcChildItem) {
//				$desc[] = $mcChildItem->getName();
//			}
//		}

		return count($desc) > 0 ? implode(", ", $desc) : null;
	}

	private function buildItemInventory(Mage_Sales_Model_Order_Item $mcOrderItem)
	{
		if ($mcOrderItem->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
			$children = $mcOrderItem->getChildrenItems();
        	$mcStockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($children[0]->getProductId());
		}
		else {
        	$mcStockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($mcOrderItem->getProductId());
		}
        if ($mcStockItem->getId() && Mage::helper('catalogInventory')->isQty($mcStockItem->getTypeId())) {
			return $mcStockItem->getQty();
        }
        return 0;
	}

	/**
	 * Return an array of order item options.
	 * @param Mage_Sales_Model_Order_Item $mcItem
	 * @return array
	 */
    private function buildItemOptions(Mage_Sales_Model_Order_Item $mcItem)
    {
        $result = array();
        if ($options = $mcItem->getProductOptions()) {
            if (isset($options['options'])) {
                $result = array_merge($result, $options['options']);
            }
            if (isset($options['additional_options'])) {
                $result = array_merge($result, $options['additional_options']);
            }
            if (!empty($options['attributes_info'])) {
                $result = array_merge($options['attributes_info'], $result);
            }
            if (!empty($options["bundle_options"])) {
                $result = array_merge($result, $options["bundle_options"]);
            }
        }
        return $result;
    }

	/**
	 * Build a URL to the merchant's view of the page.
	 * @param string $page
	 * @param string $query
	 * @return string
	 */
	private function buildMerchantUrl($page, $query = null)
	{
		$url = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, true) . $page;
		if (isset($query) && trim($query) != "")
			$url = $url . "?" . $query;
		return $url;
	}

	/**
	 * Build a list of current inventory counts for all items in an order.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array
	 */
	private function buildOrderInventoryResult(Mage_Sales_Model_Order $mcOrder)
	{
		$inventoryResult = new InventoryResult();

		try
		{
			foreach ($mcOrder->getAllItems() as $mcOrderItem)
			{
				if (!$mcOrderItem->isDummy(false))
				{
					$itemCount = new ItemCount();
					$itemCount->Count = $this->buildItemInventory($mcOrderItem);
					$itemCount->ItemId = $mcItem->getId();
					$inventoryResult->ItemCounts[] = $itemCount;
				}
			}

			$inventoryResult->Success = true;
		}
		catch (Exception $ex)
		{
			$inventoryResult->Success = false;
			$inventoryResult->Message = $ex->getMessage();
		}

		return $inventoryResult;
	}

	/**
	 * Build a list of Items from the order.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array
	 */
	private function buildOrderItems(Mage_Sales_Model_Order $mcOrder)
	{
		$items = array();
		$orderBy = 0;

		foreach ($mcOrder->getAllItems() as $mcOrderItem)
		{
			if ($mcOrderItem->isDummy(false)) {
				continue;
			}
			$item = $this->buildItem($mcOrderItem);
			$item->OrderBy = $orderBy++;
			$items[] = $item;
		}

		return $items;
	}

	/**
	 * Build a list of order payments.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array]
	 */
	private function buildOrderPayments(Mage_Sales_Model_Order $mcOrder)
	{
		$payments = array();

		foreach ($mcOrder->getInvoiceCollection() as $mcInvoice) {
			if ($mcInvoice->getState() == Mage_Sales_Model_Order_Invoice::STATE_PAID) {
				$namedAmount = new NamedAmount();
				$namedAmount->Amount = (float) $mcInvoice->getGrandTotal();
				$namedAmount->Name = "Invoice #" . $mcInvoice->getIncrementId();
				$payments[] = $namedAmount;
			}
		}

		foreach ($mcOrder->getCreditmemosCollection() as $mcCredit)
		{
			if ($mcCredit->getState() == Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED) {
				$namedAmount = new NamedAmount();
				$namedAmount->Amount = - ((float) $mcCredit->getGrandTotal());
				$namedAmount->Name = "Refund #" . $mcCredit->getIncrementId();
				$payments[] = $namedAmount;
			}
		}

		return $payments;
	}

	/**
	 * Build an OrderResult from a Magento Commerce order.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return OrderResult
	 */
	private function buildOrderResult(Mage_Sales_Model_Order $mcOrder, $includePackages = true)
	{
		$orderResult = new OrderResult();
		$orderResult->Order = new ShippingAgentOrder();
		$orderResult->Order->OrderId = $mcOrder->getId();

		try
		{
			$orderResult->Order->BillingAddress = $this->buildAddress($mcOrder->getBillingAddress());
			$orderResult->Order->Charges = $this->buildCharges($mcOrder);
			$orderResult->Order->Customer = $this->buildCustomer($mcOrder);
			$orderResult->Order->CustomerUrl = $this->buildCustomerUrl("index.php/sales/order/view/order_id/" . $mcOrder->getId() . "/");
			$orderResult->Order->Items = $this->buildOrderItems($mcOrder);
			$orderResult->Order->MerchantUrl = $this->buildMerchantUrl("index.php/admin/sales_order/view/order_id/" . $mcOrder->getId() . "/");
			$orderResult->Order->OrderDate = $mcOrder->getCreatedAtDate()->getIso();
			$orderResult->Order->OrderNumber = $mcOrder->getRealOrderId();
			$orderResult->Order->OrderStatus = $mcOrder->getStatusLabel();
			$orderResult->Order->OrderTotal = (float) $mcOrder->getGrandTotal();
			$orderResult->Order->Packages = $includePackages ? $this->buildPackageResults($mcOrder) : null;
			$orderResult->Order->Payments = $this->buildOrderPayments($mcOrder);
			$orderResult->Order->PaymentStatus = $this->buildPaymentStatus($mcOrder);
			$orderResult->Order->ShippingStatus = $this->buildShippingStatus($mcOrder);
			$orderResult->Success = true;
		}
		catch (Exception $ex)
		{
			$orderResult->Success = false;
			$orderResult->Message = $ex->getMessage();
		}

		return $orderResult;
	}

	/**
	 * Calculate the insurance value of the items.
	 *
	 * @param Mage_Sales_Model_Order_Shipment $mcShipment
	 * @return float
	 */
	private function buildPackageInsuranceValue(Mage_Sales_Model_Order_Shipment $mcShipment)
	{
		$value = (float) 0;

		foreach ($mcShipment->getAllItems() as $mcItem)
		{
			$value += $mcItem->getQty() * $mcItem->getPrice();
		}

		return (float) $value;
	}

	/**
	 * Build a list of Items from the order.
	 *
	 * @param Mage_Sales_Model_Order_Shipment $mcShipment
	 * @return array
	 */
	private function buildPackageItems(Mage_Sales_Model_Order_Shipment $mcShipment)
	{
		$items = array();
		$orderBy = 0;

		foreach ($mcShipment->getAllItems() as $mcShipmentItem)
		{
			$mcOrderItem = $mcShipmentItem->getOrderItem();
			if ($mcOrderItem->isDummy(true) || $mcOrderItem->getIsVirtual()) {
                continue;
			}
            $item = $this->buildItem($mcOrderItem);
            $item->LineTotal = $mcShipmentItem->getQty() * $item->Price;
            $item->OrderBy = $orderBy++;
            $item->Quantity = $mcShipmentItem->getQty();
            $item->Weight = $mcShipmentItem->getQty() * $mcOrderItem->getWeight();
            $items[] = $item;
		}

		return $items;
	}

	/**
	 * Build a PackageResult from a Magento Commerce order. There is one
	 * package per Magento shipment.
	 *
	 * @param Mage_Sales_Model_Order_Shipment $mcShipment
	 * @param Mage_Sales_Model_Order_Shipment_Track $mcTrack
	 * @return PackageResult
	 */
	private function buildPackageResult(Mage_Sales_Model_Order_Shipment $mcShipment, Mage_Sales_Model_Order_Shipment_Track $mcTrack)
	{
		$packageResult = new PackageResult();
		$packageResult->Package = new Package();
		$packageResult->Package->OrderId = $mcShipment->getOrder()->getId();
		$packageResult->Package->PackageId = $mcShipment->getId() ? $mcShipment->getId() . "," . $mcTrack->getId() : "";

		try
		{
			if ($mcShipment->getId()) {
				$packageResult->Package->CustomerUrl = $this->buildCustomerUrl("index.php/sales/order/shipment/order_id/" . $mcShipment->getOrder()->getId() . "/");
			}
			$packageResult->Package->Destination = $this->buildShippingAddress($mcShipment->getOrder()->getShippingAddress());
			$packageResult->Package->Instructions = $mcShipment->getOrder()->getCustomerNote();
			$packageResult->Package->InsuranceValue = $this->buildPackageInsuranceValue($mcShipment);
			$packageResult->Package->Items = $this->buildPackageItems($mcShipment);
			if ($mcShipment->getId()) {
				$packageResult->Package->MerchantUrl = $this->buildCustomerUrl("index.php/admin/sales_order_shipment/view/shipment_id/" . $mcShipment->getId() . "/order_id/" . $mcShipment->getOrder()->getId() . "/");
			}
			else {
				$packageResult->Package->MerchantUrl = $this->buildCustomerUrl("index.php/admin/sales_order/view/order_id/" . $mcShipment->getOrder()->getId() . "/");
			}
			$packageResult->Package->RateOptionName = $mcShipment->getOrder()->getShippingDescription();
			if ($mcShipment->hasCreatedAt()) {
				$packageResult->Package->ShipDate = $mcShipment->getCreatedAtDate()->getIso();
				$packageResult->Package->Shipped = true;
				$packageResult->Package->TrackingNumber = $mcTrack->getNumber();
				$carrierInstance = Mage::getSingleton('shipping/config')->getCarrierInstance($mcTrack->getCarrierCode());
				if ($carrierInstance && $carrierInstance->isTrackingAvailable()) {
					$packageResult->Package->TrackingProviderId = $mcTrack->getCarrierCode();
					$packageResult->Package->TrackingProviderName = $carrierInstance->getConfigData('title');
				}
			}
			$packageResult->Package->Weight = $this->buildPackageWeight($mcShipment);
			$packageResult->Package->Warehouse = $this->buildWarehouseAddress($mcShipment->getStore());

			$packageResult->Success = true;

			if (count($mcShipment->getAllTracks()) > 1) {
				$packageResult->Package->Items = null;
				$packageResult->Package->Weight = 0;
				$packageResult->Message = "There are no items shown in this package because the Magento shipment was split into more than one package and Magento does not record which items were in each package.";
			}
		}
		catch (Exception $ex)
		{
			$packageResult->Success = false;
			$packageResult->Message = $ex->getMessage();
		}

		return $packageResult;
	}

	/**
	 * Build a list of PackageResults from a Magento Commerce order.
	 * One package for each Track.
	 *
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array
	 */
	private function buildPackageResults(Mage_Sales_Model_Order $mcOrder)
	{
		$packages = array();

        $mcShipments = array();
        foreach ($mcOrder->getShipmentsCollection() as $mcShipment) {
            if (!$mcShipment->isDeleted()) {
                $mcShipments[] = $mcShipment;
                if (count($mcShipment->getAllTracks()) == 0) {
                    $mcTrack = Mage::getModel('sales/order_shipment_track');
                    $mcShipment->addTrack($mcTrack);
                    $mcShipment->save();
                }
            }
        }

        // Create one more shipment to capture all the remaining
        // unshipped items. If this is the first time through, this
        // will include all the shippable items.
        $mcShipment = $this->buildShipment($mcOrder);
        if ($mcShipment != null) {
            $mcShipments[] = $mcShipment;
            $mcTrack = Mage::getModel('sales/order_shipment_track');
            $mcShipment->addTrack($mcTrack);
        }

        // Create one Package for each tracking number.
		foreach ($mcShipments as $mcShipment) {
			foreach ($mcShipment->getAllTracks() as $mcTrack) {
				$packages[] = $this->buildPackageResult($mcShipment, $mcTrack);
			}
		}
		return $packages;
	}

	/**
	 * Add up the item weights;
	 *
	 * @param Mage_Sales_Model_Order_Shipment $mcShipment
	 * @return float
	 */
	private function buildPackageWeight(Mage_Sales_Model_Order_Shipment $mcShipment)
	{
		$value = (float) 0;
		foreach ($mcShipment->getAllItems() as $mcShipmentItem)
		{
			$mcOrderItem = $mcShipmentItem->getOrderItem();
			if (!$mcOrderItem->isDummy(true)) {
				$value += $mcShipmentItem->getQty() * $mcOrderItem->getWeight();
			}
		}
		return (float) $value;
	}

	/**
	 * Magento does not have a built-in payment status, so this calculates
	 * a reasonable value.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return string
	 */
	private function buildPaymentStatus(Mage_Sales_Model_Order $mcOrder)
	{
		if ($mcOrder->getTotalDue() == 0)
			return self::PAYMENT_STATUS_PAID;
		if (abs($mcOrder->getGrandTotal() - $mcOrder->getTotalDue()) < 0.001)
			return self::PAYMENT_STATUS_UNPAID;
		return self::PAYMENT_STATUS_PARTIALLY_PAID;
	}

    /**
     * Create a shipment with the remaining items in the order.
     *
     * @param Mage_Sales_Model_Order $mcOrder
     * @return Mage_Sales_Model_Order_Shipment
     */
    private function buildShipment(Mage_Sales_Model_Order $mcOrder)
    {
    	if (!$mcOrder->canShip()) {
    		return null;
    	}

        $converter = Mage::getModel('sales/convert_order');
        $mcShipment = $converter->toShipment($mcOrder);
        $totalQty = 0;
        foreach ($mcOrder->getAllItems() as $mcOrderItem) {
        	if ($mcOrderItem->isDummy(true) || $mcOrderItem->getIsVirtual()) {
        		continue;
        	}
            
            $qty = $mcOrderItem->getQtyToShip();
            if ($qty > 0) {
                $mcOrderShipmentItem = $converter->itemToShipmentItem($mcOrderItem);
                $mcOrderShipmentItem->setQty($qty);
                $mcShipment->addItem($mcOrderShipmentItem);
                $totalQty += $qty;
            }
        }
        if ($totalQty > 0) {
            $mcShipment->setTotalQty($totalQty);
            return $mcShipment;
        }
        return null;
    }

	/**
	 * Build a shipping address from an order address array.
	 * Note that Shipper expects 2 letter country, state, and
	 * province codes.
	 * @param Mage_Sales_Model_Order_Address $mcAddress
	 * @return ShippingAddress
	 */
	private function buildShippingAddress(Mage_Sales_Model_Order_Address $mcAddress)
	{
		$address = new ShippingAddress();
		$address->AddressId = $mcAddress->getId();
		$address->City = $mcAddress->getCity();
		$address->Company = $mcAddress->getCompany();
		$address->Country = $mcAddress->getCountry();
		$address->Email = $mcAddress->getEmail();
		$address->Fax = $mcAddress->getFax();
		$address->FirstName = $mcAddress->getFirstname();
		$address->LastName = $mcAddress->getLastname();
		$address->Line1 = $mcAddress->getStreet1();
		$address->Line2 = $mcAddress->getStreet2();
		$address->Phone = $mcAddress->getTelephone();
		$address->PostalCode = $mcAddress->getPostcode();
		$address->Region = $mcAddress->getRegionCode();
		$address->Residential = self::SHIPPING_ADDRESS_RESIDENTIAL;
		return $address;
	}

	/**
	 * Magento does not have a shipping status. This function calculates
	 * a reasonable value.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return string
	 */
	private function buildShippingStatus(Mage_Sales_Model_Order $mcOrder)
	{
        if ($mcOrder->getIsVirtual())
        {
            return self::SHIPPING_STATUS_UNSHIPPABLE;
        }

        $qtyOrdered = 0;
        $qtyToShip = 0;
        foreach ($mcOrder->getAllItems() as $mcItem)
        {
        	if ($mcItem->getParentItem())
        	{
        		continue;
        	}
        	if ($mcItem->getIsVirtual())
        	{
        		continue;
        	}
			$qtyOrdered += $mcItem->getQtyOrdered();
			$qtyToShip += $mcItem->getQtyToShip();
        }

        if ($qtyOrdered == 0)
        {
        	return self::SHIPPING_STATUS_UNSHIPPABLE;
        }
        if ($qtyToShip == 0)
        {
        	return self::SHIPPING_STATUS_SHIPPED;
        }
        if ($qtyOrdered == $qtyToShip)
        {
        	return self::SHIPPING_STATUS_UNSHIPPED;
        }
        return self::SHIPPING_STATUS_PARTIALLY_SHIPPED;
    }

    private function buildTrackingProvidersResult()
    {
    	$carriers = array();
        $carrierInstances = Mage::getSingleton('shipping/config')->getAllCarriers();
        foreach ($carrierInstances as $code => $carrier) {
            if ($carrier->isTrackingAvailable()) {
            	$provider = new TrackingProvider();
            	$provider->Name = $carrier->getConfigData('title');
            	$provider->TrackingProviderId = $code;
            	$carriers[] = $provider;
            }
        }
        return $carriers;
    }

	/**
	 * Update the package with shipping information from Shipper.
	 * @param stdClass $update
	 * @return UpdateResult
	 */
	private function buildUpdateResult(stdClass $update)
	{
		$notes = array();

		$updateResult = new UpdateResult();
		try
		{
			$mcOrder = Mage::getModel('sales/order')->load($update->OrderId);
			if ($mcOrder->getId() != $update->OrderId)
				throw new Exception("Invalid Order ID '" . $update->OrderId . "'.");
                
            if (isset($update->PackageId) && strpos($update->PackageId, ",")) {
                $ids = explode(",", $update->PackageId);
                $mcShipmentId = trim($ids[0]);
                $mcTrackId = trim($ids[1]);
            }

            if (isset($mcShipmentId)) {
                $mcShipment = Mage::getModel('sales/order_shipment')->load($mcShipmentId);
                if ($mcShipment->getId() != $mcShipmentId) {
                    throw new Exception("Invalid Package ID '" . $parameters->packageId . "'.");
                }
            } else {
                $mcShipment = $this->buildShipment($mcOrder);
                if ($mcShipment == null) {
                    throw new Exception("Shipment not recorded because the order was already completely shipped.");
                }
            }

            if (isset($mcTrackId)) {
                $mcTrack = Mage::getModel("sales/order_shipment_track")->load($mcTrackId);
                if ($mcTrack->getId() != $mcTrackId || $mcTrack->getShipment()->getId() != $mcShipmentId) {
                    throw new Exception("Invalid Package ID '" . $parameters->packageId . "'.");
                }
            } else if (count($mcShipment->getAllTracks()) == 0) {
                $mcTrack = Mage::getModel("sales/order_shipment_track");
                $mcShipment->addTrack($mcTrack);
            } else {
                $mcTracks = $mcShipment->getAllTracks();
                $mcTrack = $mcTracks[0];
            }

			// Shipper can correct the shipping address (e.g. fix typos and find
			// the ZIP+4 code). If the destination address is included in the update
			// this code will update the order address. You may want to update the
			// customers address book as well.
            $mcShippingAddress = $mcOrder->getShippingAddress();
			if (isset($update->Destination) && !$this->addressesAreEqual($update->Destination, $mcShippingAddress))
			{
                if (isset($update->Destination->City)) {
                    $mcShippingAddress->setCity($update->Destination->City);
                } else {
                    $mcShippingAddress->setCity("");
                }
                if (isset($update->Destination->Company)) {
                    $mcShippingAddress->setCompany($update->Destination->Company);
                } else {
                    $mcShippingAddress->setCompany("");
                }
                if (isset($update->Destination->Country)) {
                    $mcShippingAddress->setCountry($update->Destination->Country);
                } else {
                    $mcShippingAddress->setCountry("");
                }
                if (isset($update->Destination->Email)) {
                    $mcShippingAddress->setEmail($update->Destination->Email);
                } else {
                    $mcShippingAddress->setEmail("");
                }
                if (isset($update->Destination->Fax)) {
                    $mcShippingAddress->setFax($update->Destination->Fax);
                } else {
                    $mcShippingAddress->setFax("");
                }
                if (isset($update->Destination->FirstName)) {
                    $mcShippingAddress->setFirstname($update->Destination->FirstName);
                } else {
                    $mcShippingAddress->setFirstname("");
                }
                if (isset($update->Destination->LastName)) {
                    $mcShippingAddress->setLastname($update->Destination->LastName);
                } else {
                    $mcShippingAddress->setLastname("");
                }
                if (isset($update->Destination->PostalCode)) {
                    $mcShippingAddress->setPostcode($update->Destination->PostalCode);
                } else {
                    $mcShippingAddress->setPostcode("");
                }
                if (isset($update->Destination->Region)) {
                    $mcShippingAddress->setRegionCode($update->Destination->Region);
                } else {
                    $mcShippingAddress->setRegionCode("");
                }
                $street = array();
                if (isset($update->Destination->Line1)) {
                    $street[] = $update->Destination->Line1;
                }
                if (isset($update->Destination->Line2)) {
                    $street[] = $update->Destination->Line2;
                }
                $mcShippingAddress->setStreet($street);
                if (isset($update->Destination->Phone)) {
                    $mcShippingAddress->setTelephone($update->Destination->Phone);
                } else {
                    $mcShippingAddress->setTelephone("");
                }
				$mcShippingAddress->save();
				$notes[] = "Updated order shipping address.";
			}

			// If Shipper sends a ship date, that means the package was shipped.
			if (isset($update->ShipDate))
			{
                $shipDate = date_create($update->ShipDate)->format("Y-m-d H:i:s");
                if (!$mcShipment->hasCreatedAt()) {
                    $mcShipment->setCreatedAt($shipDate);
                    $mcShipment->save();
                }
				$notes[] = "Package was shipped on " . $shipDate . ".";

                if (isset($update->TrackingNumber))
                {
                    if (isset($update->TrackingProviderId)) {
                        $mcTrack->setCarrierCode($update->TrackingProviderId);
                        $mcCarrier = Mage::getSingleton('shipping/config')->getCarrierInstance($mcTrack->getCarrierCode());
                        if ($mcCarrier) {
                            $mcTrack->setTitle($mcCarrier->getConfigData('title'));
                        }
                    } else {
                        $mcTrack->setCarrierCode(Mage_Sales_Model_Order_Shipment_Track::CUSTOM_CARRIER_CODE);
                    }
                    $mcTrack->setNumber($update->TrackingNumber);
                    $mcTrack->save();
                    $notes[] = "Tracking number set to " . $update->TrackingNumber . ".";
                }

                if (isset($update->ShippingCost))
                {
                    $notes[] = "Shipping cost was " . $mcOrder->formatPriceTxt($update->ShippingCost) . ".";
                }

                if (count($notes) > 0)
                {
                    foreach ($notes as $note) {
                        $mcShipment->addComment($note);
                    }
                    $mcShipment->save();
                }

                foreach ($mcShipment->getAllItems() as $mcOrderShipmentItem) {
                    $mcOrderItem = $mcOrderShipmentItem->getOrderItem();
                    $qtyShipped = $mcOrderItem->getQtyShipped() + $mcOrderShipmentItem->getQty();
                    $mcOrderItem->setQtyShipped($qtyShipped);
                    $mcOrderItem->save();
                }
                $mcOrder->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true, "Order shipped by Shipping Agent.");
                $mcOrder->save();
            }

			// Return a fresh copy of the order, the package, and the notes.
			$mcOrder->load($update->OrderId);
			$updateResult->OrderResult = $this->buildOrderResult($mcOrder, false);
            $updateResult->PackageResult = $this->buildPackageResult($mcShipment, $mcTrack);
			if (count($notes) > 0)
				$updateResult->Message = implode(" ", $notes);
			else
				$updateResult->Message = "No change.";
			$updateResult->Success = true;
		}
		catch (Exception $ex)
		{
			$updateResult->Message = $ex->getMessage();
			$updateResult->Success = false;
		}

		return $updateResult;
	}

	private function buildWarehouseAddress(Mage_Core_Model_Store $mcStore)
	{
		$warehouse = new Address();
		$warehouse->AddressId = $mcStore->getId();
		$warehouse->City = $mcStore->getConfig("shipping/origin/city");
		$warehouse->Country = $mcStore->getConfig("shipping/origin/country_id");
		$warehouse->PostalCode = $mcStore->getConfig("shipping/origin/postcode");

		$regions = Mage::getModel("directory/country")->setId($warehouse->Country)->getRegions();
		$regionId = $mcStore->getConfig("shipping/origin/region_id");
		foreach ($regions as $region) {
			if ($region->getId() ==  $regionId) {
				$warehouse->Region = $region->getName();
				break;
			}
		}
		return $warehouse;
	}

	private function getFloat($text)
	{
		if (!isset($text) || trim($text) == '') return (float) 0;
		$nocommas = str_replace(",", "", $text);
		$amount = str_replace("$", "", $nocommas);
		return (float) $amount;
	}
}
?>